package com.wonghiu.framework.proxy;

import com.wonghiu.framework.transport.HRpcRequestTransport;
import com.wonghiu.framework.common.enums.HRpcErrorMessageEnum;
import com.wonghiu.framework.common.enums.HRpcResponseCodeEnum;
import com.wonghiu.framework.common.exception.HRpcException;
import com.wonghiu.framework.config.HRpcServiceConfig;
import com.wonghiu.framework.exchange.HRpcRequest;
import com.wonghiu.framework.exchange.HRpcResponse;
import com.wonghiu.framework.transport.netty.client.NettyRpcClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * @Author wonghiu
 * @Date 2021/8/10 21:05
 * @Version 1.0.1
 */
@Slf4j
public class ConsumerProxy implements InvocationHandler {

    private static final String INTERFACE_NAME = "interfaceName";
    private static final int TRY_TIMES = 3;

    /**
     * Used to send requests to the server.And there are two implementations: socket and netty
     */
    private final HRpcRequestTransport rpcRequestTransport;
    private final HRpcServiceConfig rpcServiceConfig;

    public ConsumerProxy(HRpcRequestTransport rpcRequestTransport, HRpcServiceConfig rpcServiceConfig) {
        this.rpcRequestTransport = rpcRequestTransport;
        this.rpcServiceConfig = rpcServiceConfig;
    }


    public ConsumerProxy(HRpcRequestTransport rpcRequestTransport) {
        this.rpcRequestTransport = rpcRequestTransport;
        this.rpcServiceConfig = new HRpcServiceConfig();
    }

    /**
     * get the proxy object
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);
    }

    /**
     * This method is actually called when you use a proxy object to call a method.
     * The proxy object is the object you get through the getProxy method.
     */
    @SneakyThrows
    @SuppressWarnings("unchecked")
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        log.info("invoked method: [{}]", method.getName());
        HRpcRequest rpcRequest = HRpcRequest.builder().methodName(method.getName())
                .parameters(args)
                .interfaceName(method.getDeclaringClass().getName())
                .paramTypes(method.getParameterTypes())
                .requestId(UUID.randomUUID().toString())
                .group(rpcServiceConfig.getGroup())
                .version(rpcServiceConfig.getVersion())
                .build();
        HRpcResponse<Object> rpcResponse = null;
        // 超时重试
        int c = 0;
        while (c<TRY_TIMES) {
            if (rpcRequestTransport instanceof NettyRpcClient) {
                CompletableFuture<HRpcResponse<Object>> completableFuture = (CompletableFuture<HRpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest);
                rpcResponse = completableFuture.get();
            }
            // 判断超时
            if(!rpcResponse.getCode().equals(HRpcResponseCodeEnum.TIMEOUT_FAIL.getCode())){
                break;
            }
            c++;
        }

        this.check(rpcResponse, rpcRequest);
        return rpcResponse.getData();
    }

    private void check(HRpcResponse<Object> rpcResponse, HRpcRequest rpcRequest) {
        if (rpcResponse == null) {
            throw new HRpcException(HRpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }

        if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {
            throw new HRpcException(HRpcErrorMessageEnum.REQUEST_NOT_MATCH_RESPONSE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }

        if (rpcResponse.getCode() == null || !rpcResponse.getCode().equals(HRpcResponseCodeEnum.SUCCESS.getCode())) {
            throw new HRpcException(HRpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
    }
}
